﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Collections;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using MapEditor.Tiles;

namespace MapEditor
{
    public class TileLayer
    {
        private readonly Texture2D tileset;

        public Texture2D[] texture;
        private int width, height, tileSize;
        public Tile[,] tiles;
        public Tile[,] nontiles;
        private List<TreeObject> list = new List<TreeObject>();
        private Node node;
        private Stream stream;
        const int QUADTREE_WIDTH = 1000;
        const int QUADTREE_HEIGHT = 1000;
        SaveFileDialog save = new SaveFileDialog();
        OpenFileDialog load = new OpenFileDialog();

        public List<Rectangle> listRect = new List<Rectangle>();
        

        public int Width
        {
            get
            {
                return width;
            }
            set
            {
                ResizeLayer(value, height);

                width = value;
            }
        }

        public int Height
        {
            get
            {
                return height;
            }
            set
            {
                ResizeLayer(width, value);

                height = value;
            }
        }

        public int TileSize
        {
            get
            {
                return tileSize;
            }
            set
            {
                tileSize = value;

                Clear();
            }
        }

      

        public TileLayer(Texture2D tileset, int width, int height, int tileSize, Texture2D[] texture)
        {
            this.texture = texture;
            this.tileset = tileset;
            this.width = width;
            this.height = height;
            this.tileSize = tileSize;
            tiles = new Tile[width, height];
            nontiles = new Tile[width * tileSize, height * tileSize];
            Clear();
        }

        public TileLayer(TileLayer layer)
        {
            this.texture = layer.texture;
            tileset = layer.tileset;
            width = layer.width;
            height = layer.height;
            tileSize = layer.tileSize;
            tiles = new Tile[width, height];
            nontiles = new Tile[width * tileSize, height * tileSize];
            Array.Copy(layer.tiles, tiles, width * height);
            Array.Copy(layer.nontiles, nontiles, height * tileSize * width * tileSize);
        }

        public Tile GetTile(int tileX, int tileY)
        {
            return tiles[tileY, tileX];
        }

        

        public void SetTile(int tileX, int tileY, Tile newCell, bool showGrid)
        {
            if (showGrid)
            {
                tiles[tileX, tileY] = newCell;

                int minX = (int)MathHelper.Max(tileX - 1, 0);
                int minY = (int)MathHelper.Max(tileY - 2, 0);

                int maxX = (int)MathHelper.Min(tileX + 1, 31);
                int maxY = (int)MathHelper.Min(tileY + 2 , 30);


                for (int i = minX * tileSize; i < maxX * tileSize + tileSize; i++)
                    for (int j = minY * tileSize; j < maxY * tileSize + tileSize; j++)
                    {
                        if (nontiles[i, j] != null)
                            if (CheckCollisionRec(nontiles[i, j].rectangle, newCell.rectangle))
                                tiles[tileX, tileY] = new EmptyTile();


                    }
            }
            else
            {
                int minX = (int)MathHelper.Max(tileX - tileSize, 0);
                int minY = (int)MathHelper.Max(tileY - (tileSize*2), 0);
                int maxX = (int)MathHelper.Min(tileX + tileSize, 1024 - 32);
                int maxY = (int)MathHelper.Min(tileY + (tileSize*2), 1024 - (32*2));
                //int maxY = (int)MathHelper.Min(tileY + (tileSize), 1024 - (32));

                for (int i = minX; i < maxX; i++)
                    for (int j = minY; j < maxY; j++)
                    {
                        if (nontiles[i, j] != null)
                        {

                            if (CheckCollisionRec(nontiles[i, j].rectangle, newCell.rectangle))
                            {
                                nontiles[tileX, tileY] = null;
                                return;
                            }

                        }

                    }

                int minXTile = (int)MathHelper.Max(tileX / tileSize, 0);
                int minYTile = (int)MathHelper.Max(tileY / tileSize -1, 0);
                int maxXTile = (int)MathHelper.Min(tileX / tileSize + 1, 31);
                int maxYTile = (int)MathHelper.Min(tileY / tileSize + 2, 30);

                for (int i = minXTile; i <= maxXTile; i++)
                    for (int j = minYTile; j <= maxYTile; j++)
                    {
                        if (tiles[i, j].type != 0)
                        {
                            if (CheckCollisionRec(tiles[i, j].rectangle, newCell.rectangle))
                            {
                                nontiles[tileX, tileY] = null;
                                return;
                            }
                        }
                    }

                nontiles[tileX, tileY] = newCell;

            }
            /*foreach (Tile nontile in nontiles)
            {
                if (nontile != null)
                {
                    if (CheckCollision(nontile.rectangle, newCell.rectangle))
                        tiles[tileY, tileX] = new EmptyTile();
                    else
                        return;
                        
                }
            } */
        }

        public void SetNonTiles(int X, int Y, NonTile newCell)
        {
            
            int minX = (int)MathHelper.Max(X - tileSize, 0);
            int minY = (int)MathHelper.Max(Y - tileSize, 0);
            int maxX = (int)MathHelper.Min(X + tileSize, 1024 - 32);
            int maxY = (int)MathHelper.Min(Y + tileSize, 1024 - 32);

            for(int i = minX; i < maxX; i++)
                for (int j = minY; j < maxY; j++)
                {
                    if (nontiles[i, j] != null)
                    {

                        if (CheckCollisionRec(nontiles[i, j].rectangle, newCell.rectangle))
                        {
                            nontiles[X, Y] = null;
                            return;
                        }
                       
                    }
                   
                }

            int minXTile = (int)MathHelper.Max(X / tileSize, 0);
            int minYTile = (int)MathHelper.Max(Y / tileSize, 0);
            int maxXTile = (int)MathHelper.Min(X / tileSize + 1, 31);
            int maxYTile = (int)MathHelper.Min(Y / tileSize + 1, 31);

            for(int i = minXTile; i <= maxXTile; i++)
                for (int j = minYTile; j <= maxYTile; j++)
                {
                    if (tiles[i, j].type != 0)
                    {
                        if (CheckCollisionRec(tiles[i, j].rectangle, newCell.rectangle))
                        {
                            nontiles[X, Y] = null;
                            return;
                        }
                    }
                }
            
            //nontiles[X, Y] = newCell;
                  

            //CheckCollisionNonTile(X, Y, newCell);
        }


        public void DrawNonTile(SpriteBatch spriteBatch, Rectangle previewRect,  Rectangle currentViewingRectangle)
        {
            int xBounds = (int)MathHelper.Min(currentViewingRectangle.Right, width);
            int yBounds = (int)MathHelper.Min(currentViewingRectangle.Bottom, height);

            int pixelRectangleX = currentViewingRectangle.X * tileSize;
            int pixelRectangleY = currentViewingRectangle.Y * tileSize;
            int pixelXBounds = xBounds * tileSize;
            int pixelYBounds = yBounds * tileSize;

            
            for (int x = pixelRectangleX; x < pixelXBounds; x++)
            {
                for (int y = pixelRectangleY; y < pixelYBounds; y++)
                {
                    if (nontiles[x, y] != null)
                    {
                        
                        //nontiles[y, x].Draw(spriteBatch, new Rectangle(y, x, tileSize, tileSize), texture[nontiles[y, x].type]);
                        nontiles[x, y].Draw(spriteBatch, nontiles[x,y].rectangle, texture[nontiles[x, y].type]);
                    }
                }
            }
        }

        public void Draw(SpriteBatch spriteBatch, Rectangle previewRect, Rectangle currentViewingRectangle)
        {
            int xBounds = (int)MathHelper.Min(currentViewingRectangle.Right, width);
            int yBounds = (int)MathHelper.Min(currentViewingRectangle.Bottom, height);


            
            for (int x = currentViewingRectangle.X; x < xBounds; x++)
            {
                for (int y = currentViewingRectangle.Y; y < yBounds; y++)
                {
                    //if (IsBetween(x, previewRect.X / tileSize, (previewRect.X + previewRect.Width) / tileSize) && IsBetween(y, previewRect.Y / tileSize, (previewRect.Y + previewRect.Height) / tileSize)) // Don't draw where the tool preview is going to be
                      //  continue;
                    /*for(int i = x* tileSize; i< x*tileSize + tileSize; i++)
                        for (int j = y * tileSize; j < y * tileSize + tileSize; j++)
                        {
                            if (nontiles[i, j]!=null)
                                return;
                            else
                                
                        }
                     */
                    //tiles[y, x].Draw(spriteBatch, new Rectangle(x * TileSize, y * TileSize, TileSize, TileSize), texture[tiles[y, x].type]);
                    //tiles[y, x].Draw(spriteBatch, tiles[y,x].rectangle, texture[tiles[y, x].type]);
                    tiles[x, y].Draw(spriteBatch, tiles[x, y].rectangle, texture[tiles[x, y].type]);
                }
            }
        }

        public void DrawQuadTree(SpriteBatch spriteBatch)
        {
            for (int i = 0; i < listRect.Count; i++)
            {
                Utility.DrawRectangle(spriteBatch,3, listRect[i], Color.Aqua);
            }
        }

        private static bool IsBetween(int actual, int lower, int upper)
        {
            return actual.CompareTo(lower) >= 0 && actual.CompareTo(upper) < 0;
        }

        public void Clear()
        {
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                    SetTile(x, y, new EmptyTile(), true);
        }

        private void ResizeLayer(int layerWidth, int layerHeight)
        {
            var newTiles = new Tile[layerHeight, layerWidth];

            for (int x = 0; x < layerWidth; x++)
                for (int y = 0; y < layerHeight; y++)
                    newTiles[y, x] = (x >= width) || (y >= height) ? new EmptyTile() : tiles[y, x];

            tiles = newTiles;
        }

        private bool CheckCollisionRec(Rectangle exist, Rectangle newCell)
        {
            if (exist.Intersects(newCell))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #region Quadtree

        List<TreeObject> SaveObjectToList()
        {
            int tempID = 0;
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    if (tiles[i, j].type != 0)
                    {
                        TreeObject tileObject = new TreeObject(tempID, i * tileSize, j * tileSize, i* TileSize + tileSize, j*TileSize + tileSize, tiles[i, j].type);
                        tempID++;
                        list.Add(tileObject);
                    }
                }
            }

            for (int i = 0; i < width * tileSize; i++)
            {
                for (int j = 0; j < height * tileSize; j++)
                {
                    if(nontiles[i,j] !=null)
                        if (nontiles[i, j].type != 0)
                        {
                            TreeObject nontileObject = new TreeObject(tempID, i, j, i + tileSize, j + tileSize, nontiles[i, j].type);
                            tempID++;
                            list.Add(nontileObject);
                        }
                }
            }
            return list;
        }

        void Build(Node node)
        {
            if ((Math.Abs(node.x1 - node.x2) < QUADTREE_WIDTH) || (Math.Abs(node.y1 - node.y2) < QUADTREE_HEIGHT) || (node.list.Count == 0))
            {
                return;
            }
            node.lt = new Node(node.x1, node.y1, (node.x1 + node.x2) / 2, (node.y1 + node.y2) / 2);
            node.lt.id = node.id * 4 + 1;
            node.lb = new Node(node.x1, (node.y1 + node.y2) / 2, (node.x1 + node.x2) / 2, node.y2);
            node.lb.id = node.id *4 + 2;
            node.rt = new Node((node.x1 + node.x2) / 2, node.y1, node.x2, (node.y1 + node.y2) / 2);
            node.rt.id = node.id * 4 + 3;
            node.rb = new Node((node.x1 + node.x2) / 2, (node.y1 + node.y2) / 2, node.x2, node.y2);
            node.rb.id = node.id * 4 + 4;

            for (int i = 0; i < node.list.Count; i++)
            {
                Clip(node.list[i], node.lt);
                Clip(node.list[i], node.lb);
                Clip(node.list[i], node.rt);
                Clip(node.list[i], node.rb);
            }

            node.list.Clear();

            Build(node.lt);
            Build(node.lb);
            Build(node.rt);
            Build(node.rb);
        }

        void Clip(TreeObject o, Node node)
        {
            int resultX = ClipX(o.x1, o.x2, node.x1, node.x2);
            int resultY = ClipX(o.y1, o.y2, node.y1, node.y2);

            if (resultX == 1 && resultY == 1)
            {
                TreeObject treeObject = new TreeObject(o.id, node.x1, node.y1, o.x2, o.y2, o.type);
                node.list.Add(treeObject);
            }

            if (resultX == 1 && resultY == 2)
            {

                TreeObject treeObject = new TreeObject(o.id, node.x1, o.y1, o.x2, o.y2, o.type);
                node.list.Add(treeObject);
            }

            if (resultX == 1 && resultY == 3)
            {
                TreeObject treeObject = new TreeObject(o.id, node.x1, o.y1, o.x2, node.y2, o.type);
                node.list.Add(treeObject);
            }

            if (resultX == 2 && resultY == 1)
            {
                TreeObject treeObject = new TreeObject(o.id, o.x1, node.y1, o.x2, o.y2, o.type);
                node.list.Add(treeObject);
            }

            if (resultX == 2 && resultY == 2)
            {
                TreeObject treeObject = new TreeObject(o.id, o.x1, o.y1, o.x1, o.y2, o.type);
                node.list.Add(treeObject);
            }

            if (resultX == 2 && resultY == 3)
            {
                TreeObject treeObject = new TreeObject(o.id, o.x1, o.y1, o.x2, node.y2, o.type);
                node.list.Add(treeObject);
            }

            if (resultX == 3 && resultY == 1)
            {
                TreeObject treeObject = new TreeObject(o.id, o.x1, node.y1, node.x2, o.y2, o.type);
                node.list.Add(treeObject);
            }

            if (resultX == 3 && resultY == 2)
            {
                TreeObject treeObject = new TreeObject(o.id, o.x1, o.y1, node.x2, o.y2, o.type);
                node.list.Add(treeObject);
            }

            if (resultX == 3 && resultY == 3)
            {
                TreeObject treeObject = new TreeObject(o.id, o.x1, o.x2, node.x2, node.y2, o.type);
                node.list.Add(treeObject);
            }
        }

        int ClipX(int x1, int x2, int x3, int x4)
        {

            if ((x1 < x3) && (x2 <= x3))
            {
                return 0;
            }
            if ((x1 <= x3) && (x2 > x3) && (x2 <= x4))
            {
                return 1;
            }
            if ((x1 >= x3) && (x1 < x4) && (x2 > x3) && (x2 <= x4))
            {
                return 2;
            }
            if ((x1 >= x3) && (x1 < x4) && (x2 > x4))
            {
                return 3;
            }
            if ((x1 >= x4) && (x2 > x4))
            {
                return 0;
            }
            return 0;
        }

        string Serialize(Node node)
        {
            string s;
            if (node.id == 0)
            {
                s = "0 " + node.x1.ToString() + " " + (height * TileSize - node.y1).ToString() + " "
                   + node.x2.ToString() + " " + (height * TileSize - node.y2).ToString();
            }
            else
            {
                //int intID = Convert.ToInt32(node.id,2);
                s = node.id + " " + node.x1.ToString() + " " + (height * TileSize - node.y1).ToString() + " "
                    + node.x2.ToString() + " " + (height * TileSize - node.y2).ToString();
            }

            for (int i = 0; i < node.list.Count; i++)
            {
                s = s + " " + node.list[i].id.ToString();
            }
            return s;
        }

        public void SaveQuadTree()
        {
            SaveObjectToList();
            Node root = FindRoot();
            root.list = list;
            root.id = 0;
            Build(root);
            save.Filter = "Txt Files (*.txt)|*.txt|All files (*.*)|*.*";
            if (save.ShowDialog() == DialogResult.OK)
            {
                if ((stream = save.OpenFile()) != null)
                {

                    using (StreamWriter w = new StreamWriter(stream))
                    {
                        WriteQuadTree(w, root);
                    }
                    stream.Close();
                }
                MessageBox.Show("QuadTree Saved!");
            }
           
        }

        void WriteQuadTree(StreamWriter w, Node node)
        {
            w.WriteLine(Serialize(node));
            if (node.lt != null)
            {
                WriteQuadTree(w, node.lt);
            }
            if (node.lb != null)
            {
                WriteQuadTree(w, node.lb);
            }
            if (node.rt != null)
            {
                WriteQuadTree(w, node.rt);
            }
            if (node.rb != null)
            {
                WriteQuadTree(w, node.rb);
            }

        }

        Node FindRoot()
        {
            int minY = list[0].y1;
            int maxY = list[0].y2;

            int minX = list[0].x1;
            int maxX = list[0].x2;

            for (int i = 1; i < list.Count; i++)
            {
                if (list[i].y1 < minY)
                {
                    minY = list[i].y1;
                }
                if (list[i].y2 > maxY)
                {
                    maxY = list[i].y2;
                }
            }

            for (int i = 1; i < list.Count; i++)
            {
                if (list[i].x1 < minX)
                {
                    minX = list[i].x1;
                }
                if (list[i].x2 > maxX)
                {
                    maxX = list[i].x2;
                }
            }

            int widthRoot = maxX - minX;
            int heightRoot = maxY - minY;

            if (widthRoot >= heightRoot)
            {
                Node node = new Node(list[0].x1, minY, list[0].x1 + widthRoot, minY + widthRoot);
                return node;
            }
            else
            {
                Node node = new Node(list[0].x1, minY, list[0].x1 + heightRoot, minY + heightRoot);
                return node;
            }

        }

        #endregion

        #region SaveLoad
        public void Save()
        {
            save.Filter = "Txt Files (*.txt)|*.txt|All files (*.*)|*.*";
            if (save.ShowDialog() == DialogResult.OK)
            {
                if ((stream = save.OpenFile()) != null)
                {
                    SaveDataToFile();
                    stream.Close();
                }
                MessageBox.Show("Saved!");
            }
        }

        private void SaveDataToFile()
        {

            using (StreamWriter w = new StreamWriter(stream))
            {
                int tempID = 0;
                w.WriteLine((width * tileSize).ToString());
                w.WriteLine((height * tileSize).ToString());
                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        int tempX = i * tileSize;
                        int tempY = (height - j) * tileSize;
                        if(tiles[i,j].type !=0)
                        {
                            w.WriteLine(tiles[i,j].type.ToString() + " " + tempX.ToString() + " " + tempY.ToString() + " tile" +" " +tempID);
                            tempID++;
                        }
                    }
                }

                for (int i = 0; i < width * tileSize; i++)
                {
                    for (int j = 0; j < height * tileSize; j++)
                    {
                        
                        int tempJ = (height * tileSize) - j;
                     
                            if(nontiles[i,j]!=null)
                            {
                                if (nontiles[i, j].type != 0)
                                {
                                    w.WriteLine(nontiles[i, j].type.ToString() + " " + i.ToString() + " " + tempJ.ToString() + " nontile" + " " + tempID);
                                    tempID++;
                                }

                            }
                    }
                }
            }
        }

        public ArrayList SplitString(string str, char ch)
        {
            ArrayList ls = new ArrayList();
            int n = str.Length;
            string line = "";
            for (int i = 0; i < n; i++)
            {
                while ((i < n) && (String.Compare(str[i].ToString(), ch.ToString()) != 0))
                {
                    line += str[i];
                    i++;
                }
                ls.Add(line);
                line = "";
            }
            return ls;
        }

        public void Load()
        {
            load.Filter = "Txt Files (*.txt)|*.txt|All files (*.*)|*.*";
            if (load.ShowDialog() == DialogResult.OK)
            {
                if ((stream = load.OpenFile()) != null)
                {
                    LoadDataFromFile();
                    stream.Close();
                }
                MessageBox.Show("Loaded!");
            }
        }


        private void LoadDataFromFile()
        {

            using (StreamReader r = new StreamReader(stream))
            {
                char[] cBuffer = new char[128];
                string s;
                ArrayList arrList = new ArrayList();

                s = r.ReadLine();
                width = Convert.ToInt32(s)/TileSize;
                s = r.ReadLine();
                height = Convert.ToInt32(s)/TileSize;
                //mapArr = new int[width, height];
                tiles = new Tile[width, height];
                nontiles = new Tile[width *TileSize, height *TileSize];

                while (s != null)
                {
                    s = r.ReadLine();
                    if (s == null)
                    {
                        break;
                    }
                    int i = 0;
                    int j = 0;
                    int type = 0;
                    string isTile = "";
                    arrList = SplitString(s, ' ');
                    type = Convert.ToInt32(arrList[0]);
                    i = Convert.ToInt32(arrList[1]);
                    j = Convert.ToInt32(arrList[2]);
                    isTile = arrList[3].ToString();
                    if (isTile == "tile")
                    {
                        TileType tileType = new TileType(TileType.DefaultName, 1);
                        Tile replacementTile = TileFactory.Get(tileType, type, i/TileSize, height - j/TileSize, true);
                        this.SetTile(i / TileSize, height - j/TileSize, replacementTile, true);
                    }
                    for (int x = 0; x < width; x++)
                        for (int y = 0; y < height; y++)
                        {
                            if (tiles[x, y] == null)
                            {
                                //TileType empty = new TileType(TileType.DefaultName, 0);
                                //Tile emptyTile = TileFactory.Get(empty, 0, x, y, true);
                                Tile emptyTile = new EmptyTile();
                                tiles[x, y] = emptyTile;
                            }

                        }
                    if (isTile == "nontile")
                    {
                        TileType tileType = new TileType(TileType.DefaultName, 1);
                        Tile replacementTile = TileFactory.Get(tileType, type, i, height *TileSize - j, false);
                        this.SetTile(i , height*TileSize - j , replacementTile, false);

                    }
                }
              
                

            }
        }

        #endregion

        #region SaveLoadQuadTree

        public void LoadQuadTree(SpriteBatch spritebatch)
        {
            load.Filter = "Txt Files (*.txt)|*.txt|All files (*.*)|*.*";
            if (load.ShowDialog() == DialogResult.OK)
            {
                if ((stream = load.OpenFile()) != null)
                {
                    using (StreamReader r = new StreamReader(stream))
                    {
                        char[] cBuffer = new char[128];
                        string s;
                        ArrayList arrList = new ArrayList();

                        s = r.ReadLine();
                        arrList = SplitString(s, ' ');
                        //g.DrawRectangle(pen, Convert.ToInt32(arrList[0]), Convert.ToInt32(arrList[1]), Convert.ToInt32(arrList[2]) - Convert.ToInt32(arrList[0]), Convert.ToInt32(arrList[3]) - Convert.ToInt32(arrList[1]));


                        
                        while (s != null)
                        {
                            s = r.ReadLine();
                            if (s == null)
                            {
                                break;
                            }
                            int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
                            arrList = SplitString(s, ' ');
                            x1 = Convert.ToInt32(arrList[1]);
                            x2 = Convert.ToInt32(arrList[3]);
                            y1 = height*TileSize - Convert.ToInt32(arrList[2]);
                            y2 = height*TileSize - Convert.ToInt32(arrList[4]);
                            //g.DrawRectangle(pen,  left + x1, top + y1,  left + x2 - x1, top + y2 - y1);
                            Rectangle rect = new Rectangle(x1, y1, x2 - x1, y2 - y1);
                            listRect.Add(rect);
                           
                            



                        }
                        
                    stream.Close();
                }
                MessageBox.Show("QuadTree Loaded!");
            }
            

            }

        }

      
        #endregion


    }
}
